package com.sky.pay.api;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.pay.api.request.RefundRequest;
import com.sky.pay.api.request.WeixinPayOrderRequest;
import com.sky.pay.common.OrderStatus;
import com.sky.pay.common.R;
import com.sky.pay.service.OrderService;
import com.sky.pay.service.RefundService;
import com.sky.pay.service.WeixinPayService;
import com.sky.pay.uitls.HttpUtil;
import com.sky.pay.uitls.WechatPay2ValidatorForRequest;
import com.sky.pay.vo.Order;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author yangsk
 */
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WeixinPayApi {
    @Autowired
    private WeixinPayService weixinPayService;
    //verifier的来源是更新平台证书
    @Autowired
    private Verifier verifier;

    @Autowired
    private OrderService orderService;

    @Autowired
    private RefundService refundService;

    //也可以换成分布式锁
    private final ReentrantLock paylock  = new ReentrantLock();
    private final ReentrantLock refundlock  = new ReentrantLock();

    /**
     * 当点击支付按钮的时候
     * 除了生成用户订单外，还需要调用微信统一下单接口，
     * 统一下单接口调用成功后，后端将微信生成商家的收款二维码返回前端页面，
     * 前端页面弹出商家收款码，用户通过扫码付款，
     * 用户付款成功后，微信会异步回调通知商家，告诉商家用户支付的情况
     * 商家收到异步消息后，商家会对异步消息进行数据签名验证(验签失败，
     * 微信按一定的时间级别再次发送通知)，数据验签成功后，对数据进行解密，
     * 修改订单状态、生成交际记录。
     * 特色情况：
     * 1、在统一下单的生成商家收款二维码，并且前端页面正常弹出二维码。
     * 但是用户没有进行支付操作，而是将其取消，然后在进行点击支付按钮操作，
     * 这个时候就需要对之前生成的订单和二维码进行进行校验，存在就返回，否则就走正常步骤。
     * 2、在第一种情况的基础上，用户需要扫码支付，在前端页面弹出商家收款二维码的时候，
     * 需要定时查询该订单的订单状态，看是否已付款，只有未付款，前端页面才能调用微信支付进行
     * 付款操作。
     * 3、用户在前端页面取消收款二维码，后端已经生成一条订单，订单状态-->未付款,
     *  用户通过查看自己的订单列表找到该订单，用户可以对这条订单进行“取消”操作。
     *  后端需要将订单状态将被改成“已取消”,同时，还需要调用微信支付的关单接口
     * 4、当用户支付成功后，由于某种原因导致商家迟迟未收到微信的异步支付成功消息，
     * 这个时候，就需要后端写一个定时任务，主动调取微信的查单接口
     * @param request
     * @return 返回的数据类型是Map
     * @throws Exception
     */
    @PostMapping("/native/order")
    public R createWeixinPayOrder(@RequestBody WeixinPayOrderRequest request) throws Exception {
        Map<String,Object> payResult = weixinPayService.nativePay(request);
        //将微信支付单生成的付款二维码和订单号返回前端页面,页面弹出此二维码，客户通过扫码支付
        return R.ok(payResult);
    }

    /**
     * 当用户通过扫码支付成功后，微信会主动调用(回调)这个方法，将支付结果告诉商家
     * @param request：微信的请求对象
     * @param response：商家响应微信回调的响应对象
     * @method: 微信回调的方式是post
     * 微信会对发起回调的请求体进行签名，所以这里需要对请求体的数据进行签名验证然后再将密文进行解密成正常的数据
     * 请求体reqeust的报文格式：
     * {
     *     "id": "EV-2018022511223320873",
     *     "create_time": "2015-05-20T13:29:35+08:00",
     *     "resource_type": "encrypt-resource",
     *     "event_type": "TRANSACTION.SUCCESS",
     *     "summary": "支付成功",
     *     "resource": {
     *         "original_type": "transaction",
     *         "algorithm": "AEAD_AES_256_GCM",
     *         "ciphertext": "",
     *         "associated_data": "",
     *         "nonce": ""
     *     }
     * }
     * @return
     */
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response)throws Exception{
        String body = "";
        Map responseMap = new HashMap<String,Object>();
        try{
            body = HttpUtil.covertData(request);
            //将body转成map
            HashMap<String,Object> resultBody = JSONObject.parseObject(body, HashMap.class);
            //消息id
            String requestId = (String)resultBody.get("id");
            //对request的请求数据进行签名验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier,body, requestId);
            if(!wechatPay2ValidatorForRequest.validate(request,body)){
                log.error("商家签名验证失败");
                response.setStatus(500);
                responseMap.put("code","ERROR");
                responseMap.put("message","签名验证失败");
                return JSON.toJSONString(responseMap);
            }
            //数据签名验证通过后，需要对接收到的参数进行解密
            log.info("签名验证通过,需要对boy的数据进行解密");
            String plainText = weixinPayService.decryptFromBodyResource(resultBody);
            HashMap<String,Object> hashMap = JSONObject.parseObject(plainText, HashMap.class);
            String outTradeNo = (String)hashMap.get("out_trade_no");
             //采用锁，防止并发通知导致生成多条相同的交易记录
            if(paylock.tryLock()){
                try{
                    Order order = orderService.getOrderStatus(outTradeNo);
                    //防止重复通知：只有订单在未支付的状态下，才能更新订状态和生成交际记录
                    if(!OrderStatus.NOPAY.equals(order.getOrderStatus())){
                        return null;
                    }
                    //更新用户订单状态：未支付-->支付成功；正常商家和微信的交易记录
                    weixinPayService.createTradeRecord(plainText);
                }finally {
                    paylock.unlock();
                }
            }
            response.setStatus(200);
            responseMap.put("code","SUCCESS");
            responseMap.put("message","成功");
            return JSON.toJSONString(responseMap);

        }catch (Exception e){
            //说明商家接收微信支付通知失败，微信会按一定时间比例(16个级别)继续回调这个接口，知道回调通知成功，
            new RuntimeException("收到的微信回调响应，商家处理消息通知异常",e);
            response.setStatus(500);
            responseMap.put("code","FAIL");
            responseMap.put("message","失败");

            return JSON.toJSONString(responseMap);
        }
    }

    /**
     * 用户主动取消订单
     * @param orderNo
     * @return
     */
    @PutMapping("/native/cancle")
    public R cancleOrder(@PathVariable("orderNo")String orderNo) throws Exception {
        weixinPayService.cancleOrder(orderNo);
        return R.ok();
    }

    /**
     * 商家定时查询微信的交易单,
     * 针对用户支付成功，商户未收到微信的异步支付成功消息，
     * 注意这个接口是专门给定时任务用的：weixinPayService.queryOrder(orderNo);
     * @param orderNo
     * @return
     */
    @GetMapping("/native/query")
    public R queryOrder(@PathVariable("orderNo")String orderNo) throws IOException {
        String result  = weixinPayService.queryOrder(orderNo);
        return R.ok(result);
    }

    /**
     * 用户退款：用户的订单支付成功后，发起退款操作
     * @param request
     * @return
     */
    @PostMapping("/native/refund")
    public R refund(@RequestBody RefundRequest request) throws IOException {
        weixinPayService.refund(request);
        return R.ok();
    }
    /**
     * 查询退款：用户向商家发起退款申请操作，商家接收到后，生成退款单并向微信发起退款请求，
     * 退款成功，由于某种原因，商家未接到微信的退款异步通知消息，这个时候商家需要启动一个
     * 定时器，根据退款单号主动查询微信的退款情况
     */

    @GetMapping("/native/query/refund")
    public R getQueryRefund() throws IOException {
        String refundNo="";
        weixinPayService.getQueryRefund(refundNo);
        return R.ok();
    }
    /**
     * 退款成功后，微信异步回调退款通知消息
     */
    @PostMapping("/refunds/notify")
    public String refundNotify(HttpServletRequest request,HttpServletResponse response){
        String bodyString = "";
        Map responseMap = new HashMap<String,Object>();
        try{
            bodyString = HttpUtil.covertData(request);
            //将body转成map
            HashMap<String,Object> resultBody = JSONObject.parseObject(bodyString, HashMap.class);
            //消息id
            String requestId = (String)resultBody.get("id");
            //对request的请求数据进行签名验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier,bodyString, requestId);
            if(!wechatPay2ValidatorForRequest.validate(request,bodyString)){
                log.error("商家签名验证失败");
                response.setStatus(500);
                responseMap.put("code","ERROR");
                responseMap.put("message","签名验证失败");
                return JSON.toJSONString(responseMap);
            }
            //数据签名验证通过后，需要对接收到的参数进行解密
            log.info("签名验证通过,需要对boy的数据进行解密");
            String plainText = weixinPayService.decryptFromBodyResource(resultBody);
            HashMap<String,Object> hashMap = JSONObject.parseObject(plainText, HashMap.class);
            String outTradeNo = (String)hashMap.get("out_trade_no");
            //采用锁，防止并发通知导致生成多条相同的交易记录
            if(refundlock.tryLock()){
                try{
                    Order order = orderService.getOrderStatus(outTradeNo);
                    //防止重复通知：只有订单在"退款中"的状态下，才能更新订状态和退款单状态
                    if(!OrderStatus.REFUND_PROCESSING.equals(order.getOrderStatus())){
                        return null;
                    }
                    //更新订单状态
                   orderService.updateOrderStatus(outTradeNo,OrderStatus.REFUND_SUCCESS.getType());
                    //更新退款单
                    refundService.updateRefundOrder(bodyString);
                }finally {
                    refundlock.unlock();
                }
            }
            response.setStatus(200);
            responseMap.put("code","SUCCESS");
            responseMap.put("message","成功");
            return JSON.toJSONString(responseMap);

        }catch (Exception e){
            //说明商家接收微信支付通知失败，微信会按一定时间比例(16个级别)继续回调这个接口，知道回调通知成功，
            new RuntimeException("收到的微信回调响应，商家处理消息通知异常",e);
            response.setStatus(500);
            responseMap.put("code","FAIL");
            responseMap.put("message","失败");

            return JSON.toJSONString(responseMap);
        }
    }

}
